﻿using MusicExplorer.Model;
using MusicExplorer.Repository.Internals;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace MusicExplorer.Repository
{
    [Id("EABD6F5B-5C10-41F3-AD2A-376234C42CB1")]
    public sealed class YoutubeRepository : RepositoryBase
    {
        private const string m_xml_ns = "http://www.w3.org/2005/Atom";
        private const string m_xml_yt_ns = "http://gdata.youtube.com/schemas/2007";
        private const string m_xml_yt_media = "http://search.yahoo.com/mrss/";

        private const string m_urlSearchPlaylist = @"http://gdata.youtube.com/feeds/api/users/{0}/playlists";
        private const string m_urlUploadedTracks = @"https://gdata.youtube.com/feeds/api/users/{0}/uploads?start-index={1}&max-results={2}&orderby=published";

        public YoutubeRepository(Model.Repository repository)
            : base(repository)
        {
            m_urlSearchEntities[EntityType.Youtube] =
                @"https://gdata.youtube.com/feeds/api/channels?q={0}&start-index={1}&max-results={2}&v=2";
            m_urlTracks[new QueryDefinition(QueryType.AllTracks, EntityType.Youtube)] =
                @"http://gdata.youtube.com/feeds/api/playlists/{0}?start-index={1}&max-results={2}&orderby=position";
        }

        protected override string GetSearchEntitiesUrl(EntityType type, string terms, int page)
        {
            return string.Format(m_urlSearchEntities[type],
                terms, 1 + (page - 1) * 10, 10);
        }

        protected override string GetTracksUrl(QueryType queryType, Entity entity, int page)
        {
            if(!string.IsNullOrEmpty(((Youtube)entity).PlaylistId))
            {
                return string.Format(m_urlTracks[new QueryDefinition(queryType, entity.Type)],
                    ((Youtube)entity).PlaylistId, 1 + (page - 1) * 20, 20);
            }
            else
            {
                return string.Format(m_urlUploadedTracks, entity.Id, 1 + (page - 1) * 20, 20);
            }
        }

        public override int StartingPage { get { return 1; } }
        public override bool IsMusicStore { get { return false; } }

        protected override List<Track> DecodeTracks(QueryType type, Entity entity, string source)
        {
            var tracks = new List<Track>();

            var doc = XDocument.Parse(source);
            XNamespace nmsp = m_xml_ns;
            XNamespace media = m_xml_yt_media;
            XNamespace yt = m_xml_yt_ns;
            var tracksElement = doc.Root.Elements(nmsp + "entry");

            foreach (var t in tracksElement)
            {
                var mediaXml = t.Element(media + "group");
                var thumbnail = mediaXml.Element(media + "thumbnail");
                var link = t.Elements(nmsp + "link").First(e => e.Attribute("rel").Value == "alternate");
                var youtubeUrl = link.Attribute("href").Value.Split(',').First().Replace("&feature=youtube_gdata", "");
                
                var id = Regex.Split(youtubeUrl, "v=").Last();

                var track = new Track(id, m_repository, TrackType.Youtube)
                {
                    Name = WebUtility.HtmlDecode(t.Element(nmsp + "title").Value),
                    Artwork = thumbnail.Attribute("url").Value,
                    PublishDate = DateTime.Parse(t.Element(nmsp + "published").Value),
                    YoutubeUrl = youtubeUrl,
                    Url = youtubeUrl
                    //track_duration = Int32.Parse(mediaXml.Element(yt + "duration").Attribute("seconds").Value)
                };

                tracks.Add(track);
            }
            return tracks;
        }

        protected override List<Youtube> DecodeYoutube(string source, CancellationToken token)
        {
            var channels = new List<Youtube>();

            XDocument doc = XDocument.Parse(source);
            XNamespace nmsp = m_xml_ns;
            XNamespace yt = m_xml_yt_ns;
            var entries = doc.Root.Elements(nmsp + "entry");

            foreach (var entry in entries)
            {
                var title = entry.Element(nmsp + "title").Value;
                var author = WebUtility.HtmlDecode(entry.Element(nmsp + "author").Element(nmsp + "name").Value);
                var id = entry.Element(nmsp + "author").Element(yt + "userId").Value;
                var name = title != author ? title + " - " + author : title;
                var playlistName = "All uploaded tracks";

                var channel = new Youtube(id, name, m_repository, null, playlistName);
                channels.Add(channel);
            }

            token.ThrowIfCancellationRequested();

            // Now check for playlist which are channels too
            Task<string>[] tasks = new Task<string>[channels.Count];
            for(var i = 0; i < channels.Count; i++)
            {
                var num = i;
                tasks[num] = Task<string>.Run(() => GetOnlineSourceAsync(string.Format(m_urlSearchPlaylist, channels[num].Id), token));
            }
            Task.WaitAll(tasks);

            token.ThrowIfCancellationRequested();

            var subChannels = new List<Youtube>();

            Youtube[] enumerableChannels = new Youtube[channels.Count];
            channels.CopyTo(enumerableChannels);
            for (int i = 0; i < enumerableChannels.Length; i++)
            {
                var task = tasks[i];
                var channel = enumerableChannels[i];
                if (!string.IsNullOrEmpty(task.Result))
                {
                    var index = channels.IndexOf(enumerableChannels[i]) + 1;
                    channels.InsertRange(index, DecodeYoutubePlaylists(task.Result, channel));
                }
            }

            return channels;
        }

        private List<Youtube> DecodeYoutubePlaylists(string source, Youtube parentChannel)
        {
            var channels = new List<Youtube>();

            var doc = XDocument.Parse(source);
            XNamespace nmsp = m_xml_ns;
            var playlists = doc.Root.Elements(nmsp + "entry");

            foreach (var p in playlists.OrderByDescending(p => DateTime.Parse(p.Element(nmsp + "published").Value)))
            {
                var title = WebUtility.HtmlDecode(p.Element(nmsp + "title").Value);
                var playlistId = p.Element(nmsp + "id").Value.Split('/').Last();

                var channel = new Youtube(parentChannel.Id, parentChannel.Name, m_repository, playlistId, title);
                channels.Add(channel);
            }
            return channels;
        }
    }
}
